local _M = {}
local ngx = ngx
local ok, json = pcall(require, "cjson.safe")
if not json then
    ok, json = pcall(require, "cjson")
end
local tostring = tostring
local str_format = string.format
local tracer = require("skywalking.tracer")

---@Mysql
---lua-resty-mysql执行前调用
---@param mysql lua-resty-mysql对象
---@param db_conf db的配置，应该包含mysql的host和port
function _M.MysqlBefore(mysql, db_conf)
    if not mysql then
        return
    end
    db_conf = db_conf or {}
    local mysql_host = str_format("%s:%s", tostring(db_conf.host or ""), tostring(db_conf.port or 3306))
    local exit_span = tracer:createExitSpan(ngx.ctx, "LuaMysqlClient", mysql_host)
    exit_span:setComponentId(exit_span.ComponentId_Constants.MysqlComponentId)
    exit_span:tag(exit_span.Tag_Constants.TagDBInstance, mysql_host)
    exit_span:tag(exit_span.Tag_Constants.TagDBType, exit_span.DbTypeMap.dbTypeMysql)
    if db_conf.database then
        exit_span:tag(exit_span.Tag_Constants.TagDBName, db_conf.database)
    end
    exit_span:setLayer(exit_span.SpanLayer.DB)
    exit_span:start(ngx.now() * 1000)
    mysql.sky_exit_span = exit_span
    return exit_span
end

---lua-mysql执行结束时调用
---@param mysql lua-resty-mysql对象
---@param sql sql语句
---@param err 错误信息 string|nil
function _M.MysqlAfter(mysql, sql, err)
    if not mysql then
        return
    end
    local sky_exit_span = mysql.sky_exit_span
    if sky_exit_span then
        if err then
            sky_exit_span:error(ngx.now() * 1000, {key = "sql_err", value = json.encode(err)})
        end

        if not sky_exit_span.end_time then        
            sky_exit_span:tag(sky_exit_span.Tag_Constants.TagXRequestID, ngx.req.get_headers()["x-request-id"] or "")
            sky_exit_span:tag(sky_exit_span.Tag_Constants.TagXUserID, ngx.req.get_headers()["x-user-id"] or "")
            if mysql.database then
                sky_exit_span:tag(sky_exit_span.Tag_Constants.TagDBName, mysql.database or "")
            end
            sky_exit_span:tag(sky_exit_span.Tag_Constants.TagDBStatement, json.encode(sql))
            sky_exit_span:finish(ngx.now() * 1000)
        end
    end
end


---@Redis
---lua-resty-redis命令执行前调用
---@param redis
---@param db_conf  db的配置，应该包含连接redis主机的host和port
function _M.RedisBefore(redis, db_conf)
    if not redis then
        return
    end
    db_conf = db_conf or {}
    local redis_host = str_format("%s:%s", tostring(db_conf.host or ""), tostring(db_conf.port or 6379))
    local exit_span = tracer:createExitSpan(ngx.ctx, "LuaRedisClient", redis_host)
    exit_span:setComponentId(exit_span.ComponentId_Constants.RedisComponentId)
    exit_span:tag(exit_span.Tag_Constants.TagDBInstance, redis_host)
    exit_span:tag(exit_span.Tag_Constants.TagDBType, exit_span.DbTypeMap.dbTypeRedis)
    exit_span:setLayer(exit_span.SpanLayer.DB)
    exit_span:start(ngx.now() * 1000)
    redis.sky_exit_span = exit_span
    return exit_span
end

---lua-resty-redis命令执行结束时调用
---@param redis lua-resty-redis对象
---@param err 错误信息 string|nil
---@param cmd 命令 set|get...
---@param ... redis执行命令的其他可选参数
function _M.RdisAfter(redis, err, cmd, ...)
    if not redis then
        return
    end
    local sky_exit_span = redis.sky_exit_span
    if sky_exit_span and not sky_exit_span.end_time then
        sky_exit_span:tag(sky_exit_span.Tag_Constants.TagDBStatement, json.encode(cmd))
        sky_exit_span:tag(sky_exit_span.Tag_Constants.TagDBBindVariables, table.concat({...}, "|"))     
        sky_exit_span:tag(sky_exit_span.Tag_Constants.TagXRequestID, ngx.req.get_headers()["x-request-id"] or "")
        sky_exit_span:tag(sky_exit_span.Tag_Constants.TagXUserID, ngx.req.get_headers()["x-user-id"] or "")
        if err then
            sky_exit_span:error(ngx.now() * 1000, {key = "redis_err", value = json.encode(err)})
        end
        sky_exit_span:finish(ngx.now() * 1000)
    end
end

return _M
